/*!
 * \file WtCtaTicker.cpp
 * \project	WonderTrader
 *
 * \author Wesley
 * \date 2020/03/30
 * 
 * \brief CTA实时行情处理器实现文件
 * 
 * 本文件实现了CTA实时行情处理器的核心功能，主要包括：
 * 1. 实时行情数据的接收和转发处理
 * 2. 基于交易时段的时间驱动机制
 * 3. 分钟线闭合事件的精确触发
 * 4. 主力合约和次主力合约的自动处理
 * 5. 多线程时间检查和事件调度
 * 6. 与数据存储模块的协调工作
 * 
 * 该实现采用了高精度时间控制和多线程并发处理，
 * 确保CTA策略能够在正确的时间点接收到准确的市场数据。
 */
#include "WtCtaTicker.h"
#include "WtCtaEngine.h"
#include "../Includes/IDataReader.h"

#include "../Share/CodeHelper.hpp"
#include "../Share/TimeUtils.hpp"
#include "../Includes/WTSSessionInfo.hpp"
#include "../Includes/IBaseDataMgr.h"
#include "../Includes/IHotMgr.h"

#include "../WTSTools/WTSLogger.h"

USING_NS_WTP;

//////////////////////////////////////////////////////////////////////////
//WtTimeTicker
/**
 * @brief 初始化行情处理器
 * @param store 数据存储接口指针
 * @param sessionID 交易时段ID
 * 
 * 设置数据存储接口和交易时段信息，获取当前系统时间作为初始时间
 */
void WtCtaRtTicker::init(IDataReader* store, const char* sessionID)
{
	_store = store;
	_s_info = _engine->get_session_info(sessionID);
	if(_s_info == NULL)
		WTSLogger::fatal_f("Session {} is invalid, CtaTicker cannot run correctly", sessionID);
	else
		WTSLogger::info_f("CtaTicker will drive engine with session {}", sessionID);

	TimeUtils::getDateTime(_date, _time);
}

/**
 * @brief 触发价格事件
 * @param curTick 当前tick数据
 * @param hotFlag 热点标志(0-普通合约,1-主力合约,2-次主力合约)
 * 
 * 将tick数据转发给CTA引擎，如果是主力或次主力合约，
 * 还会创建对应的热点合约tick数据并转发
 */
void WtCtaRtTicker::trigger_price(WTSTickData* curTick, uint32_t hotFlag /* = 0 */)
{
	if (_engine )
	{
		std::string stdCode = curTick->code();
		_engine->on_tick(stdCode.c_str(), curTick);

		if (hotFlag!=0)
		{
			WTSTickData* hotTick = WTSTickData::create(curTick->getTickStruct());
			std::string hotCode = (hotFlag == 1) ? CodeHelper::stdCodeToStdHotCode(stdCode.c_str()) : CodeHelper::stdCodeToStd2ndCode(stdCode.c_str());
			hotTick->setCode(hotCode.c_str(), hotCode.size());
			_engine->on_tick(hotCode.c_str(), hotTick);
			hotTick->release();
		}
	}
}

/**
 * @brief 处理实时行情数据
 * @param curTick 当前tick数据
 * @param hotFlag 热点标志
 * 
 * 核心的tick数据处理函数，负责：
 * 1. 时间序列检查和处理
 * 2. 分钟线周期管理
 * 3. K线闭合事件触发
 * 4. 引擎时间同步
 * 5. 下次检查时间计算
 */
void WtCtaRtTicker::on_tick(WTSTickData* curTick, uint32_t hotFlag /* = 0 */)
{
	if (_thrd == NULL)
	{
		trigger_price(curTick, hotFlag);
		return;
	}

	uint32_t uDate = curTick->actiondate();
	uint32_t uTime = curTick->actiontime();

	if (_date != 0 && (uDate < _date || (uDate == _date && uTime < _time)))
	{
		//如果行情时间小于本地时间，直接触发价格事件
		trigger_price(curTick, hotFlag);
		return;
	}

	_date = uDate;
	_time = uTime;

	uint32_t curMin = _time / 100000;
	uint32_t curSec = _time % 100000;
	uint32_t minutes = _s_info->timeToMinutes(curMin);
	bool isSecEnd = _s_info->isLastOfSection(curMin);
	if (isSecEnd)
	{
		minutes--;
	}
	minutes++;
	uint32_t rawMin = curMin;
	curMin = _s_info->minuteToTime(minutes);	

	if (_cur_pos == 0)
	{
		//如果当前时间是0，则直接赋值即可
		_cur_pos = minutes;
	}
	else if (_cur_pos < minutes)
	{
		//如果已记录的分钟小于新的分钟，则需要先触发闭合事件
		//这时需要先触发闭合，再修改平台时间和价格
		if (_last_emit_pos < _cur_pos)
		{
			//触发数据回放模块
			StdUniqueLock lock(_mtx);

			//先更新时间戳
			_last_emit_pos = _cur_pos;

			uint32_t thisMin = _s_info->minuteToTime(_cur_pos);
			
			bool bEndingTDate = false;
			uint32_t offMin = _s_info->offsetTime(thisMin, true);
			if (offMin == _s_info->getCloseTime(true))
				bEndingTDate = true;

			WTSLogger::info("Minute Bar %u.%04u Closed by data", _date, thisMin);
			if (_store)
				_store->onMinuteEnd(_date, thisMin, bEndingTDate ? _engine->getTradingDate() : 0);

			//触发调度
			_engine->on_schedule(_date, thisMin);

			if(bEndingTDate)
				_engine->on_session_end();
		}

		//By Wesley @ 2022.02.09
		//先更新时间，再调用trigger_price
		//无论分钟是否有切换，更新时间都是对的
		if (_engine)
		{
			_engine->set_date_time(_date, curMin, curSec, rawMin);
			_engine->set_trading_date(curTick->tradingdate());
		}
		trigger_price(curTick, hotFlag);

		_cur_pos = minutes;
	}
	else
	{
		//如果分钟没有变化，则直接触发价格事件即可
		trigger_price(curTick, hotFlag);
		if (_engine)
			_engine->set_date_time(_date, curMin, curSec, rawMin);
	}

	uint32_t sec = curSec / 1000;
	uint32_t msec = curSec % 1000;
	uint32_t left_ticks = (60 - sec) * 1000 - msec;
	_next_check_time = TimeUtils::getLocalTimeNow() + left_ticks;
}

/**
 * @brief 启动行情处理器
 * 
 * 启动CTA行情处理器的主要流程：
 * 1. 初始化CTA引擎
 * 2. 计算当前交易日
 * 3. 触发交易时段开始事件
 * 4. 启动独立的时间检查线程
 * 
 * 时间检查线程负责在没有行情数据时自动触发分钟线闭合事件，
 * 确保策略能够按时接收到K线闭合通知
 */
void WtCtaRtTicker::run()
{
	if (_thrd)
		return;

	_engine->on_init();

	uint32_t curTDate = _engine->get_basedata_mgr()->calcTradingDate(_s_info->id(), _engine->get_date(), _engine->get_min_time(), true);
	_engine->set_trading_date(curTDate);

	_engine->on_session_begin();

	//先检查当前时间, 如果时间未到则等待

	_thrd.reset(new StdThread([this](){
		while(!_stopped)
		{
			uint32_t offTime = _s_info->offsetTime(_engine->get_min_time(), true);

			if (_time != UINT_MAX && _s_info->isInTradingTime(_time / 100000, true))
			{
				std::this_thread::sleep_for(std::chrono::milliseconds(10));
				uint64_t now = TimeUtils::getLocalTimeNow();

				if (now >= _next_check_time && _last_emit_pos < _cur_pos)
				{
					//触发数据回放模块
					StdUniqueLock lock(_mtx);

					//先更新时间戳
					_last_emit_pos = _cur_pos;

					uint32_t thisMin = _s_info->minuteToTime(_cur_pos);
					_time = thisMin*100000;//需要将原值转换为毫秒

					//如果thisMin为0, 说明是前一天的最后一分钟, 说明实际是第二天, 要加1
					//同时为时间序列235959xxx, 也要转换为0
					if (thisMin == 0)
					{
						uint32_t lastDate = _date;
						_date = TimeUtils::getNextDate(_date);
						_time = 0;
						WTSLogger::info("Data automatically changed at time 00:00: %u -> %u", lastDate, _date);
					}

					bool bEndingTDate = false;
					uint32_t offMin = _s_info->offsetTime(thisMin, true);
					if (offMin == _s_info->getCloseTime(true))
						bEndingTDate = true;

					WTSLogger::info("Minute bar %u.%04u closed automatically", _date, thisMin);
					if (_store)
						_store->onMinuteEnd(_date, thisMin, bEndingTDate ? _engine->getTradingDate() : 0);

					//触发调度
					_engine->on_schedule(_date, thisMin);

					if (bEndingTDate)
						_engine->on_session_end();

					//145959000
					if (_engine)
						_engine->set_date_time(_date, thisMin, 0);
				}
			}
			else //if(offTime >= _s_info->getOpenTime(true) && offTime <= _s_info->getCloseTime(true))
			{
				//如果已经过了交易时间，但还没有触发闭合事件，说明是下一日的最后一分钟，直接触发闭合事件
				uint32_t total_mins = _s_info->getTradingMins();
				if(_time != UINT_MAX && _last_emit_pos != 0 && _last_emit_pos < total_mins && offTime >= _s_info->getCloseTime(true))
				{
					WTSLogger::warn("Tradingday %u will be ended forcely, last_emit_pos: %u, time: %u", _engine->getTradingDate(), _last_emit_pos.fetch_add(0), _time);

					//触发数据回放模块
					StdUniqueLock lock(_mtx);

					//先更新时间戳
					_last_emit_pos = total_mins;

					bool bEndingTDate = true;
					uint32_t thisMin = _s_info->getCloseTime(false);
					uint32_t offMin = _s_info->getCloseTime(true);

					WTSLogger::info("Minute bar %u.%04u closed automatically", _date, thisMin);
					if (_store)
						_store->onMinuteEnd(_date, thisMin, _engine->getTradingDate());

					//触发调度
					_engine->on_schedule(_date, thisMin);

					_engine->on_session_end();

				}
				else
				{
					std::this_thread::sleep_for(std::chrono::seconds(10));
				}
			}
		}
	}));
}

/**
 * @brief 停止行情处理器
 * 
 * 设置停止标志并等待时间检查线程退出，
 * 确保所有资源得到正确清理
 */
void WtCtaRtTicker::stop()
{
	_stopped = true;
	if (_thrd)
		_thrd->join();
}

/**
 * @brief 判断是否在交易时间内
 * @return 是否在交易时间内
 * 
 * 根据当前时间和交易时段信息判断是否处于交易时间，
 * 用于控制策略的运行状态
 */
bool WtCtaRtTicker::is_in_trading() const 
{
	if (_s_info == NULL)
		return false;

	return _s_info->isInTradingTime(_time/100000, true);
}

/**
 * @brief 将时间转换为分钟数
 * @param uTime 时间(HHMMSS格式)
 * @return 从交易开始的分钟数
 * 
 * 将HHMMSS格式的时间转换为从交易开始的分钟数，
 * 用于时间计算和分钟线管理
 */
uint32_t WtCtaRtTicker::time_to_mins(uint32_t uTime) const
{
	if (_s_info == NULL)
		return uTime;

	return _s_info->timeToMinutes(uTime, true);
}